/*
 * @Author: hongbin
 * @Date: 2025-05-03 14:40:39
 * @LastEditors: hongbin
 * @LastEditTime: 2025-05-04 11:21:51
 * @Description:
 */
import { ThreeHelper } from "..";
import * as THREE from "three";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
import { OutputPass } from "three/examples/jsm/postprocessing/OutputPass.js";
import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader.js";
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";
import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass.js";

const BLOOM_SCENE = 1;

const bloomLayer = new THREE.Layers();
bloomLayer.set(BLOOM_SCENE);

export class UserEffectComposer {
    selectedObjects: Object3D[] = [];
    darkMaterial = new THREE.MeshBasicMaterial({ color: "black" });
    materials: Record<string, THREE.Material> = {};

    constructor() {
        const helper = ThreeHelper.instance;
        const { renderer, scene, camera } = helper;

        const pixel = renderer.getPixelRatio();

        const fullWidth = innerWidth * pixel;
        const fullHeight = innerHeight * pixel;

        const composer = new EffectComposer(renderer);

        composer.renderToScreen = false;

        const renderPass = new RenderPass(scene, camera);
        composer.addPass(renderPass);

        const outlinePass = new OutlinePass(
            new THREE.Vector2(window.innerWidth, window.innerHeight),
            scene,
            camera
        );

        outlinePass.visibleEdgeColor = new THREE.Color("#5511ff");
        outlinePass.hiddenEdgeColor = new THREE.Color("#11ff55");

        // composer.addPass(outlinePass);

        // strength, radius, threshold
        const bloomPass = new UnrealBloomPass(
            new THREE.Vector2(window.innerWidth, window.innerHeight),
            1,
            0.2,
            0.1
        );

        composer.addPass(bloomPass);

        const mixPass = new ShaderPass(
            new THREE.ShaderMaterial({
                uniforms: {
                    baseTexture: { value: null },
                    bloomTexture: { value: composer.renderTarget2.texture },
                },
                vertexShader: `
                            varying vec2 vUv;
        
                            void main() {
        
                                vUv = uv;
        
                                gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        
                            }
                        `,
                fragmentShader: `
                            uniform sampler2D baseTexture;
                            uniform sampler2D bloomTexture;
        
                            varying vec2 vUv;
        
                            void main() {
        
                                gl_FragColor = ( texture2D( baseTexture, vUv ) + texture2D( bloomTexture, vUv ) );
        
                            }
                        `,
                defines: {},
            }),
            "baseTexture"
        );
        mixPass.needsSwap = true;

        const finalComposer = new EffectComposer(renderer);
        finalComposer.addPass(renderPass);
        finalComposer.addPass(mixPass);

        const resolution = new THREE.Vector2(fullWidth, fullHeight);

        const outputPass = new OutputPass();
        finalComposer.addPass(outputPass);

        const effectFXAA = new ShaderPass(FXAAShader);
        effectFXAA.uniforms["resolution"].value.set(
            1 / fullWidth,
            1 / fullHeight
        );
        finalComposer.addPass(effectFXAA);

        helper.addResizeListen(() => {
            const pixel = renderer.getPixelRatio();

            const fullWidth = innerWidth * pixel;
            const fullHeight = innerHeight * pixel;

            composer.setSize(innerWidth, innerHeight);
            finalComposer.setSize(innerWidth, innerHeight);

            effectFXAA.uniforms["resolution"].value.set(
                1 / fullWidth,
                1 / fullHeight
            );
        });

        this.addSelectedObjects = (Object3D: Object3D) => {
            this.selectedObjects.push(Object3D);
            outlinePass.selectedObjects = this.selectedObjects;
        };

        this.setSelectedObjects = (...args: Object3D[]) => {
            if (args && args.length) {
                outlinePass.selectedObjects = args;
            } else {
                outlinePass.selectedObjects = [];
            }
        };

        const darkenNonBloomed = (obj: Object3D) => {
            if (obj.isMesh && bloomLayer.test(obj.layers) === false) {
                this.materials[obj.uuid] = obj.material;
                obj.material = obj.userData.darkMaterial || this.darkMaterial;
            }
        };

        const restoreMaterial = (obj: Object3D) => {
            if (this.materials[obj.uuid]) {
                obj.material = this.materials[obj.uuid];
                delete this.materials[obj.uuid];
            }
        };

        helper.render = () => {
            scene.traverse(darkenNonBloomed);
            composer.render();
            scene.traverse(restoreMaterial);
            finalComposer.render();
        };
    }

    addSelectedObjects(Object3D: Object3D) {}
    setSelectedObjects(...args: Object3D[]) {}

    /**
     * 切换是否被UnrealBloomPass渲染
     */
    toggleBloom(...objects: THREE.Object3D[]) {
        objects.forEach((object) => {
            // 切换layers
            object.layers.toggle(BLOOM_SCENE);
        });
    }

    setBloom(...objects: THREE.Object3D[]) {
        objects.forEach((object) => {
            object.layers.set(BLOOM_SCENE);
        });
    }
}
